/**
 * \file
 * \author  michal.nezerka@gmail.com
 */

#include <iostream>
#include <iomanip>
#include <math.h>
#include <wx/debug.h>

#include "geoutils.h"

using namespace wildwood;

/// GeoPos -------------------------
std::ostream& wildwood::operator<<(std::ostream &output, const GeoPos &pos)
{
    output << "GeoPos (lat=" << pos.mLat << " lon=" << pos.mLon << ")";
    return output;
}

int GeoPos::operator==(const GeoPos &pos) const
{
    if (mLat != pos.mLat) return false;
    if (mLon != pos.mLon) return false;
    return true;
}

/// TilePos -------------------------
wxString TilePos::toString()
{
    return wxString::Format(wxT("TilePos(zoom: %d (%d.%d, %d.%d)"), mZoom, mX, mInX, mY, mInY);
}

void TilePos::move(const int deltaX, const int deltaY)
{
    //std::cout << "moving tile (" << deltaX << " " << deltaY << ")" << std::endl;

    int newX = (mX * GeoUtils::TILE_SIZE + mInX) + deltaX;
    int newY = (mY * GeoUtils::TILE_SIZE + mInY) + deltaY;
    mX = newX / GeoUtils::TILE_SIZE;
    mY = newY / GeoUtils::TILE_SIZE;
    mInX = newX % GeoUtils::TILE_SIZE;
    mInY = newY % GeoUtils::TILE_SIZE;
}

void TilePos::zoomIn(const unsigned int steps)
{
    if (mZoom + steps > 17)
        return;

    mZoom += steps;
    int n = pow(2.0, steps);
    int g =  GeoUtils::TILE_SIZE / n;
    mX = (mX * n) + mInX / g;
    mY = (mY * n) + mInY / g;
    mInX = (mInX % g) * n;
    mInY = (mInY % g) * n;
}

void TilePos::zoomOut(const unsigned int steps)
{
    if (mZoom - steps < 0)
        return;

    mZoom -= steps;
    int n = pow(2.0, steps);
    int g =  GeoUtils::TILE_SIZE / n;
    mInX = mInX / n + (mX % n) * g;
    mInY = mInY / n + (mY % n) * g;
    mX = mX / n;
    mY = mY / n;
}

TilePos& TilePos::operator=(const TilePos &pos)
{
    mX = pos.mX;
    mY = pos.mY;
    mInX = pos.mInX;
    mInY = pos.mInY;
    mZoom = pos.mZoom;
    return *this;
}

int TilePos::operator==(const TilePos &pos) const
{
    if (mX != pos.mX) return 0;
    if (mY != pos.mY) return 0;
    if (mZoom != pos.mZoom) return 0;
    return 1;
}

int TilePos::operator<(const TilePos &pos) const
{
    if (mX == pos.mX && mY == pos.mY && mZoom < pos.mZoom) return 1;
    if (mX == pos.mX && mY < pos.mY) return 1;
    if (mX < pos.mX ) return 1;
    return 0;
}

std::ostream& wildwood::operator<<(std::ostream &output, const TilePos &pos)
{
    output << "TilePos(zoom:" << pos.mZoom << " (" << pos.mX << "." << pos.mInX << " " << pos.mY << "." << pos.mInY<< "))";
    return output;
}

/// GeoUtils -------------------------

// http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
// http://www.codeproject.com/Articles/14793/How-Google-Map-Works
TilePos GeoUtils::gpsPos2TilePos(const GeoPos pos, const unsigned int zoom)
{
    TilePos result;
    //correct the latitude to go from 0 (north) to 180 (south),
    // instead of 90(north) to -90(south)

    double tiles = pow(2, zoom);
    double latRad = pos.getLat() * M_PI / 180;


    double x = (pos.getLon() + 180) / 360 * tiles;
    double y = (1 - (log(tan(latRad) + 1 / cos(latRad)) / M_PI)) / 2 * tiles;
    result.mX = x;
    result.mY = y;
    result.mInX = (x - result.mX) * TILE_SIZE;
    result.mInY = (y - result.mY) * TILE_SIZE;
    result.mZoom = zoom;

    return result;
}

GeoPos GeoUtils::tilePos2GeoPos(const TilePos pos)
{
    GeoPos result;

    // longitude
	result.setLon((pos.mX + ((double)pos.mInX / TILE_SIZE)) / pow(2.0, pos.mZoom) * 360.0 - 180);

	// latitude
	//double n = M_PI - 2.0 * M_PI * ((double)pos.mY + (pos.mInY / TILE_SIZE)) / pow(2.0, pos.mZoom);
    //result.mLat = 180.0 / M_PI * atan(0.5 * (exp(n) - exp(-n)));
    double n = pow(2, pos.mZoom);
    double tileY = pos.mY + ((double)pos.mInY / TILE_SIZE);
    double latRad = atan(sinh(M_PI * (1 - 2 * tileY / n)));
    result.setLat(latRad * 180.0 / M_PI); // rad -> deg

    return result;
}



